<template>
  <el-menu ref="menu" class="my-menu" v-bind="$attrs" v-on="$listeners">
    <!-- 传入的menu  -->
    <slot />
    <!-- ...按钮 -->
    <sub-menu v-if="ellipsis" :list="overflowedElements">
      <slot name="ellipsis" />
    </sub-menu>
  </el-menu>
</template>

<script>
import ResizeObserver from 'resize-observer-polyfill'
import subMenu from './subMenu.vue'

const cloneVNode = (vnode, deep) => {
  const { componentOptions } = vnode
  const { data } = vnode
  let listeners = {}
  if (componentOptions && componentOptions.listeners) {
    listeners = { ...componentOptions.listeners }
  }

  let on = {}
  if (data && data.on) {
    on = { ...data.on }
  }
  const cloned = new vnode.constructor(
    vnode.tag,
    data ? { ...data, on } : data,
    vnode.children,
    vnode.text,
    vnode.elm,
    vnode.context,
    componentOptions ? { ...componentOptions, listeners } : componentOptions,
    vnode.asyncFactory,
  )
  cloned.ns = vnode.ns
  cloned.isStatic = vnode.isStatic
  cloned.key = vnode.key
  cloned.isComment = vnode.isComment
  cloned.fnContext = vnode.fnContext
  cloned.fnOptions = vnode.fnOptions
  cloned.fnScopeId = vnode.fnScopeId
  cloned.isCloned = true
  if (deep) {
    if (vnode.children) {
      cloned.children = cloneVNodes(vnode.children, true)
    }
    if (componentOptions && componentOptions.children) {
      componentOptions.children = cloneVNodes(componentOptions.children, true)
    }
  }
  return cloned
}

function cloneVNodes(vnodes, deep) {
  const len = vnodes.length
  const res = new Array(len)
  // eslint-disable-next-line no-plusplus
  for (let i = 0; i < len; i++) {
    // eslint-disable-next-line no-use-before-define
    res[i] = cloneVNode(vnodes[i], deep)
  }
  return res
}

const getWidth = (elem) => {
  // content + padding + border
  let width =
    elem &&
    typeof elem.getBoundingClientRect === 'function' &&
    elem.getBoundingClientRect().width
  if (width) {
    const computedStyle = getComputedStyle(elem)
    // width + marginLeft + marginRight
    width =
      width +
      parseFloat(computedStyle.marginLeft) +
      parseFloat(computedStyle.marginRight)
    width = +width.toFixed(6)
  }
  return width || 0
}
const setStyle = (elem, styleProperty, value) => {
  if (elem && typeof elem.style === 'object') {
    elem.style[styleProperty] = value
  }
}

export default {
  name: 'Menu',
  components: {
    subMenu,
  },
  data() {
    return {
      // 所有menu宽度总和
      originalTotalWidth: 0,
      resizeObserver: null,
      // 最后一个可展示menu的下标
      lastVisibleIndex: undefined,
      // 溢出的subMenus
      overflowedItems: [],
      overflowedElements: [],
      // 所有menu宽度集合
      menuItemSizes: [],
      // 所有menu集合
      ulChildrenNodes: [],
      // 原始slots.defaule备份
      originSlots: [],
      originItems: {},
      currentIndex: '',
    }
  },
  computed: {
    ellipsis() {
      return this.$attrs?.mode === 'horizontal'
    },
  },
  mounted() {
    // 保存原始数据 防止Item增删导致选中状态丢失
    if (this.$refs.menu) {
      this.originItems = Object.create(this.$refs.menu.items)
    }
    if (!this.ellipsis) return
    // 备份slots.default
    this.originSlots = cloneVNodes(
      this.$slots.default.filter((vnode) => vnode.tag),
    )
    // 拿到...按钮
    // eslint-disable-next-line prefer-destructuring
    this.lastChild = [].slice.call(this.$el.children, -1)[0]
    // 拿到所有li  去除 空template
    this.ulChildrenNodes = [].slice.call(this.$el.children, 0, -1)
    // 保存每个menu的宽度
    this.menuItemSizes = [].slice
      .call(this.ulChildrenNodes)
      .map((c) => getWidth(c))
    // 计算menu宽度总和
    this.originalTotalWidth = this.menuItemSizes.reduce(
      (acc, cur) => acc + cur,
      0,
    )
    // 注册监听事件
    this.$nextTick(() => {
      this.setChildrenWidthAndResize()
      if (this.$attrs.mode === 'horizontal') {
        const menuUl = this.$el
        if (!menuUl) return
        this.resizeObserver = new ResizeObserver((entries) => {
          entries.forEach(this.setChildrenWidthAndResize)
        })
        this.resizeObserver.observe(menuUl)
      }
    })
  },
  updated() {
    this.$refs.menu.items = Object.create(this.originItems)
  },
  methods: {
    setChildrenWidthAndResize() {
      if (this.$attrs.mode !== 'horizontal' || !this.$el) return
      const { lastChild } = this
      // ...按钮的宽度
      const overflowedIndicatorWidth = getWidth(lastChild)
      // this.originSlots.forEach((item) => {
      //   console.log(item.componentOptions.propsData.index);
      // });
      // 拿到所有slots.default
      this.$slots.default = [...this.originSlots]
      // 获取el-menu宽度
      const width = getWidth(this.$el)
      // 可展示menu宽度总和
      let currentSumWidth = 0
      // 最后一个可展示menu的下标
      let lastVisibleIndex
      // 如果宽度溢出
      if (this.originalTotalWidth > width) {
        lastVisibleIndex = -1
        this.menuItemSizes.forEach((liWidth) => {
          currentSumWidth += liWidth
          if (currentSumWidth + overflowedIndicatorWidth <= width) {
            lastVisibleIndex += 1
          }
        })
      }
      // 过滤menu相关dom
      this.overflowedElements = this.$slots.default.filter(
        (c, index) => index > lastVisibleIndex,
      )
      setStyle(
        this.lastChild,
        'display',
        lastVisibleIndex === undefined ? 'none' : 'inline-block',
      )
      // 去除隐藏的menu 解决hover时 被隐藏的menu弹窗同时出现问题
      if (lastVisibleIndex !== undefined) {
        const list = this.$slots.default.filter(
          (vnode, index) => index <= lastVisibleIndex,
        )
        this.$slots.default = cloneVNodes(list)
      }
    },
  },
}
</script>
